home *** CD-ROM | disk | FTP | other *** search
/ ...taking it to the Macs! / ...taking it to the Macs!.iso / Extras / ActiveX Mac SDK / ActiveX SDK / Container Common / cpropbag.cpp < prev    next >
Text File  |  1997-01-03  |  18KB  |  663 lines

  1. //
  2. //  CPropBag.CPP
  3. //
  4. //  Copyright (C) Microsoft Corporation, 1996
  5. //
  6.  
  7. #include "headers.h"
  8.  
  9.  
  10. #define PBCONTENTS "%%pbcontents%%"
  11.  
  12. //======================================================================
  13. //
  14. CPropertyBag::CPropertyBag()
  15.    :    m_cRef(1), m_pPropertiesTail(NULL), 
  16.     m_pPropertiesHead(NULL), m_cProperties(0), m_pbContents(0)
  17. {
  18.     // This is a standalone COM object...
  19.     // increase the ref count on the DLL so the DLL knows not to unload
  20.     // until the last object has been destroyed
  21.  
  22.     DLLAddRef();
  23.  
  24. }
  25.  
  26. //======================================================================
  27. //
  28. CPropertyBag::~CPropertyBag()
  29. {
  30.     DLLRelease();
  31.     deletePropertyList();
  32.  
  33.     //
  34.     //  BUGBUG: davidna 5/7/96: Beta1 Hack.
  35.     //
  36.     //  m_pbContents exists only if the OC knows of the %%PBCONTENTS%% name
  37.     //  and reads it.
  38.     //  Hopefully this just means the JavaOC
  39.     //
  40.     if(m_pbContents)
  41.     {
  42.         ULONG x =0, y = 2 * m_cProperties;
  43.  
  44.         char far * *pContents = (char **) ((ULONG)m_pbContents + sizeof(ULONG));
  45.  
  46.         // The storage for m_pbContents goes like this:
  47.         // We have pointers to the name/value pairs following the 
  48.         // the count (ULONG) of items (or pairs)
  49.         // we allocated storage for all the values so we must now set them free
  50.         
  51.  
  52.         // pContents[0] is the first item name and
  53.         // pContents[1] is the first item value
  54.         // so bump past the name
  55.         pContents++;    
  56.  
  57.         for(x = 0; x < y; x+=2)
  58.         {
  59.             if(pContents[x])
  60.                 GTR_FREE(pContents[x]); // free the allocation for the value
  61.         }
  62.         GTR_FREE(m_pbContents);
  63.     }
  64.  
  65. }
  66. /*******************************************************************
  67.  
  68.     NAME:       CPropertyBag::AddRef
  69.  
  70.     SYNOPSIS:   Standard implementation of IUnknown::AddRef.
  71.  
  72. ********************************************************************/
  73. STDMETHODIMP_(ULONG) CPropertyBag::AddRef(void)
  74. {
  75.     return ++m_cRef;
  76. }
  77.  
  78.  
  79. /*******************************************************************
  80.  
  81.     NAME:       CPropertyBag::Release
  82.  
  83.     SYNOPSIS:   Standard implementation of IUnknown::Release
  84.  
  85. ********************************************************************/
  86. STDMETHODIMP_(ULONG) CPropertyBag::Release(void)
  87. {
  88.  
  89.     if (--m_cRef == 0)
  90.     {
  91.         delete this;
  92.         return 0;
  93.     }
  94.     return m_cRef;
  95. }
  96. /*******************************************************************
  97.  
  98.     NAME:       CPropertyBag::QueryInterface
  99.  
  100.     SYNOPSIS:   Standard implementation of IUnknown::QueryInterface.
  101.  
  102.     NOTES:      
  103.  
  104. ********************************************************************/
  105. STDMETHODIMP CPropertyBag::QueryInterface(REFIID riid, LPVOID *ppvObj)
  106. {
  107.     // ppvObj must not be NULL
  108.     ASSERT(ppvObj != NULL);
  109.  
  110.     if (ppvObj == NULL)
  111.         return E_INVALIDARG;
  112.  
  113.     *ppvObj = NULL;
  114.  
  115.     if (riid == IID_IUnknown)
  116.         *ppvObj = this;
  117.     else
  118.     if (riid == IID_IPropertyBag)
  119.         *ppvObj = this;
  120.     else
  121.         return E_NOINTERFACE;  // Otherwise, don't delegate to HTMLObj!!
  122.  
  123.     if (*ppvObj != NULL)  // Should always be non-NULL at this point, but just to be safe...
  124.         ((LPUNKNOWN)*ppvObj)->AddRef();
  125.  
  126.     return S_OK;
  127. }
  128.  
  129. //======================================================================
  130. //
  131. // Asks the property bag to read the property named with pwcPropName 
  132. // into the caller-initialized VARIANT in pVar.  
  133. //
  134. //
  135. STDMETHODIMP_(HRESULT) CPropertyBag::Read
  136. (
  137.     LPCOLESTR pszPropName,
  138.     VARIANT *pVar,
  139.     IErrorLog *pErrorLog
  140. )
  141. {
  142. #pragma unused (pErrorLog)
  143.  
  144.     if(NULL == m_pPropertiesHead)
  145.         return E_INVALIDARG;
  146.  
  147.     ASSERT(NULL != pszPropName);
  148.     if(NULL == pszPropName)
  149.         return E_POINTER;
  150.  
  151.     ASSERT(NULL != pVar);
  152.     if(NULL == pVar)
  153.         return E_POINTER;
  154.  
  155.     HRESULT hr;
  156.     PROPERTYLIST *pCurProp = m_pPropertiesHead;
  157.     
  158. #if 0
  159.     if(0 == strcmp(pszPropName, PBCONTENTS))
  160.     {
  161.         hr = GetPropertyBagContents(pVar,pErrorLog);
  162.         return hr;
  163.     }
  164. #endif
  165.       
  166.     while(pCurProp != NULL)
  167.     {
  168.         // Paranoia
  169.         ASSERT((NULL != pCurProp->pProperty) && (NULL != pCurProp->pProperty->pszName));
  170.         if(((NULL != pCurProp->pProperty) && (NULL != pCurProp->pProperty->pszName)))
  171.         {
  172.             if(0 == strcmp(pszPropName, pCurProp->pProperty->pszName) )
  173.             {
  174.                 // Caller didn't specify
  175.                 if(pVar->vt == VT_EMPTY)
  176.                     pVar->vt =     VT_I4;      // assume long?
  177.  
  178.                 hr = VariantChangeType(pVar, pCurProp->pProperty->pvarValue, 0, pVar->vt);
  179.     
  180.                 break;
  181.             }
  182.         }
  183.         pCurProp = (PROPERTYLIST *)pCurProp->pNext;
  184.     }
  185.  
  186.     // at the end of the list?
  187.     if(NULL == pCurProp)
  188.         return E_INVALIDARG;
  189.  
  190.     if(SUCCEEDED(hr))
  191.         return S_OK;
  192.     else
  193.         return E_FAIL; // didn't find it
  194. }
  195.  
  196. //======================================================================
  197. //
  198. // Asks the property bag to read the property named with pszPropName 
  199. // into the caller-initialized VARIANT in pVar.  
  200. //
  201. //
  202. BOOL CPropertyBag::find
  203. (
  204.     const char *pszPropName,
  205.     PROPERTY * *ppProp
  206. )
  207. {
  208.     if(NULL == m_pPropertiesHead)
  209.         return FALSE;
  210.  
  211.     ASSERT(NULL != pszPropName);
  212.     if(NULL == pszPropName)
  213.         return FALSE;
  214.  
  215.  
  216.     PROPERTYLIST *pCurProp = m_pPropertiesHead;
  217.     
  218.     while(pCurProp != NULL)
  219.     {
  220.         // Paranoia
  221.         ASSERT((NULL != pCurProp->pProperty) && (NULL != pCurProp->pProperty->pszName));
  222.         if(((NULL != pCurProp->pProperty) && (NULL != pCurProp->pProperty->pszName)))
  223.         {
  224.             if(0 == strcmp(pszPropName, pCurProp->pProperty->pszName) )
  225.             {
  226.     
  227.                 *ppProp = pCurProp->pProperty;
  228.                 return TRUE;
  229.             }
  230.         }
  231.         pCurProp = (PROPERTYLIST *)pCurProp->pNext;
  232.     }
  233.     return FALSE; // didn't find it
  234. }
  235.  
  236. //======================================================================
  237. //
  238. // Asks the property bag to save the property named with 
  239. // pszPropName using the type and value in the 
  240. // caller-initialized VARIANT in pVar.
  241. //
  242. STDMETHODIMP_(HRESULT) CPropertyBag::Write
  243. (
  244.     LPCOLESTR pwcPropName, 
  245.     VARIANT *pVar
  246. )
  247. {
  248.     PROPERTY *pProperty = new(PROPERTY);
  249.     pProperty->pvarValue = new(VARIANT);
  250.  
  251.     VariantInit(pProperty->pvarValue);
  252.     
  253.     // make a copy 
  254.     VariantCopy(pProperty->pvarValue, pVar);
  255.  
  256.     pProperty->pszName = (char*)GTR_MALLOC((strlen(pwcPropName) + 1)*sizeof(char));
  257.     strcpy(pProperty->pszName, pwcPropName);
  258.     
  259.     addToPropertyList(pProperty);
  260.  
  261.     return S_OK;
  262. }
  263.  
  264. //======================================================================
  265. //
  266. BOOL CPropertyBag::addToPropertyList(PROPERTY *pProperty)
  267. {
  268.     PROPERTYLIST * pNewProp = new(PROPERTYLIST);
  269.  
  270.     if(NULL == m_pPropertiesHead)
  271.     {
  272.         m_pPropertiesHead = pNewProp;
  273.         m_pPropertiesHead->pPrev = NULL;
  274.         m_pPropertiesHead->pNext = NULL;
  275.         m_pPropertiesTail = m_pPropertiesHead;
  276.     }
  277.     else
  278.         m_pPropertiesTail->pNext = pNewProp;
  279.  
  280.     // add it to the list
  281.     pNewProp->pProperty = pProperty;
  282.     pNewProp->pPrev = m_pPropertiesTail;
  283.     pNewProp->pNext = NULL;
  284.     m_pPropertiesTail = pNewProp;
  285.  
  286.     m_cProperties++;
  287.     return TRUE;
  288. }
  289.  
  290. //======================================================================
  291. //
  292. BOOL CPropertyBag::deletePropertyList()
  293. {
  294.     if(NULL == m_pPropertiesHead)
  295.         return TRUE;
  296.  
  297.     PROPERTYLIST *pCurProp = m_pPropertiesHead;
  298.  
  299.     while(pCurProp->pNext != NULL)
  300.     {
  301.         if(pCurProp->pProperty->pvarValue->vt == VT_BSTR &&
  302.                     pCurProp->pProperty->pvarValue->bstrVal)
  303.             CoTaskMemFree(pCurProp->pProperty->pvarValue->bstrVal);
  304.         SAFEDELETE( pCurProp->pProperty->pvarValue );
  305.         if(pCurProp->pProperty->pszName)
  306.             CoTaskMemFree( pCurProp->pProperty->pszName );
  307.         SAFEDELETE( pCurProp->pProperty );
  308.  
  309.         pCurProp = (PROPERTYLIST *)pCurProp->pNext;
  310.  
  311.         SAFEDELETE( pCurProp->pPrev );
  312.     }
  313.  
  314.     // remove the last one
  315.     if(pCurProp->pProperty->pvarValue->vt == VT_BSTR &&
  316.                 pCurProp->pProperty->pvarValue->bstrVal)
  317.         CoTaskMemFree(pCurProp->pProperty->pvarValue->bstrVal);
  318.     SAFEDELETE( pCurProp->pProperty->pvarValue );
  319.     if(pCurProp->pProperty->pszName)
  320.         CoTaskMemFree( pCurProp->pProperty->pszName );
  321.     SAFEDELETE( pCurProp->pProperty );
  322.     SAFEDELETE( pCurProp );
  323.  
  324.     m_pPropertiesHead = NULL;
  325.     m_pPropertiesTail = NULL;
  326.     return TRUE;
  327. }
  328.  
  329.  
  330. void CPropertyBag::AddParam(const char *name, const char *value )
  331. {
  332.     int        length;
  333.     
  334.     //  Add the param to the property bag
  335.     VARIANT *pVar = new(VARIANT);
  336.     
  337.     // Fill in the VARIANT structure
  338.     //
  339.     VariantInit(pVar);
  340.  
  341.     length = strlen((char *) value);
  342.  
  343.     // VARIANT:BSTR's must be allocated with SysAllocString
  344.     //          (Copy 2)
  345.     pVar->vt = VT_BSTR;
  346.     //  A BSTR is a VB counted string where the first DWORD is the
  347.     //  length of the string, not including the null terminator,
  348.     //  followed by a null-terminated string.
  349.     pVar->bstrVal = (char*) CoTaskMemAlloc(sizeof(DWORD) + length + 1);
  350.     *((LPDWORD) pVar->bstrVal) = (DWORD) length;
  351.     memcpy(pVar->bstrVal + sizeof(DWORD), value, length + 1);
  352.  
  353.     ASSERT(NULL != pVar->bstrVal);
  354.     if(NULL == pVar->bstrVal)
  355.         return;
  356.  
  357.     // This is a wrapper that handles the CString 
  358.     // to OLE string conversion
  359.     this->Write(name, pVar);
  360.  
  361.     CoTaskMemFree(pVar->bstrVal);
  362.     // pPropertyBag->Write() made a copy  (3 copies all told, a bit excessive I think, necessary?)
  363.     // so SAFEDELETE() this now
  364.     SAFEDELETE( pVar );
  365.  
  366.     return;
  367. }
  368.  
  369. #if 0
  370. //======================================================================
  371. //
  372. // set the ExtentX & ExtentY values for the property bag
  373. // ulWidth & ulHeight are given in pixels.
  374. //
  375. void CPropertyBag::setExtents(ULONG ulWidth, ULONG ulHeight)
  376. {
  377.     ASSERT(pSite != NULL);
  378.     if(NULL == pSite)
  379.         return;
  380.  
  381.     PROPERTY * pProp;
  382.  
  383.     BSTR pBstr[2] = {NULL,NULL};
  384.     char szVal[20];
  385.  
  386.     CPropertyBag *pPropertyBag = NULL;
  387.  
  388.     //  See if this CSite already has a Property Bag
  389.     pPropertyBag = pSite->GetPropertyBag();
  390.     
  391.     //  If not create a new one and set it to the CSite
  392.     //  The CSite method will addRef it in either case
  393.     //
  394.     if (NULL == pPropertyBag)
  395.     {
  396.         // Create a brand new property bag
  397.         pPropertyBag = new( CPropertyBag());
  398.         ASSERT(NULL != pPropertyBag);
  399.         if(NULL == pPropertyBag)
  400.             return;
  401.         pSite->SetPropertyBag(pPropertyBag);
  402.     }  
  403.     else
  404.     {
  405.         // Do these exist already?
  406.         if(pPropertyBag->find("_extentX", &pProp) 
  407.             && pPropertyBag->find("_extentY", &pProp))
  408.             return;
  409.     }
  410.  
  411.     VARIANT *pVar[] = {new(VARIANT), new(VARIANT) };
  412.  
  413.     ASSERT((NULL != pVar[0]) && (NULL != pVar[1]));
  414.     if    ((NULL == pVar[0]) || (NULL == pVar[1]))
  415.     {
  416.         if(pVar[0])
  417.             SAFEDELETE(pVar[0]);
  418.         if(pVar[1])
  419.             SAFEDELETE(pVar[1]);
  420.         return;
  421.     }
  422.  
  423.     VariantInit(pVar[0]);
  424.     VariantInit(pVar[1]);
  425.  
  426.     pVar[0]->vt = VT_BSTR;
  427.     pVar[1]->vt = VT_BSTR;
  428.  
  429.     SIZEL szPix={ulWidth,ulHeight}, szHim={0,0};
  430.     XformSizeInPixelsToHimetric(NULL, &szPix, &szHim);
  431.  
  432.     // allocates pBstr
  433.     Ansi2Unicode(_ultoa( szHim.cx, szVal, 10 ), &pBstr[0]);
  434.     Ansi2Unicode(_ultoa( szHim.cy, szVal, 10 ), &pBstr[1]);
  435.  
  436.     ASSERT((NULL != pBstr[0]) && (NULL != pBstr[1]));
  437.     if    ((NULL == pBstr[0]) || (NULL == pBstr[1]))
  438.         return;
  439.  
  440.     // VARIANT:BSTR's must be allocated with SysAllocString
  441.     pVar[0]->bstrVal = SysAllocString(pBstr[0]);
  442.     pVar[1]->bstrVal = SysAllocString(pBstr[1]);
  443.  
  444.  
  445.     ASSERT((NULL != pVar[0]->bstrVal) && (NULL != pVar[1]->bstrVal));
  446.     if    ((NULL == pVar[0]->bstrVal) || (NULL == pVar[1]->bstrVal))
  447.     {
  448.         if(pVar[0]->bstrVal)
  449.             SAFEDELETE(pVar[0]->bstrVal);
  450.         if(pVar[1])
  451.             SAFEDELETE(pVar[1]->bstrVal);
  452.  
  453.         if(pVar[0])
  454.             SAFEDELETE(pVar[0]);
  455.         if(pVar[1])
  456.             SAFEDELETE(pVar[1]);
  457.         return;
  458.     }
  459.  
  460.     // This is a wrapper that handles the CString 
  461.     // to OLE string conversion
  462.     pPropertyBag->lpstrWrite("_extentX", pVar[0]);
  463.     pPropertyBag->lpstrWrite("_extentY", pVar[1]);
  464.  
  465.     // SAFEDELETE() these now
  466.     SAFEDELETE( pVar[0]);
  467.     SAFEDELETE( pVar[1]);
  468.  
  469.     // Don't need these any longer
  470.     if(pBstr[0])
  471.         GTR_FREE(pBstr[0]);
  472.  
  473.     if(pBstr[1])
  474.         GTR_FREE(pBstr[1]);
  475. }
  476.  
  477. //======================================================================
  478. //
  479. //  Note:  BUGBUG only name & value are used in this interim implementation
  480. //
  481. //
  482. //
  483. void HText_addParamToPropertyBag(HText *text, const char *name, const char *type, 
  484.                                              const char *value,const char *valueRef, 
  485.                                              const char *attr, const int iInsertTagElement )
  486. {
  487.     if(NULL == name)
  488.         return;
  489.  
  490.     if(NULL == value)
  491.         return;
  492.  
  493.     // Find the object we're associated with, using iInsertTagElement
  494.     if (iInsertTagElement <= -1)        // we don't have an association
  495.         return;                         // </INSERT> with no <INSERT>
  496.  
  497.     // Get a pointer to the Objects Element
  498.     ELEMENT *pElement = &(text->w3doc->aElements[iInsertTagElement]);
  499.     ASSERT(pElement != NULL);
  500.     if(NULL == pElement)
  501.         return;
  502.  
  503.     // Get the Object
  504.     CInsertableObject *pObj = (CInsertableObject *)pElement->pInsert;
  505.     if ( NULL == pObj)
  506.         return;
  507.     
  508.     //  Get the objects CSite
  509.     CSite *pSite = NULL;
  510.     CPropertyBag *pPropertyBag = NULL;
  511.     
  512.      HRESULT hr = pObj->GetSite(&pSite);
  513.     ASSERT(SUCCEEDED(hr) && pSite != NULL);
  514.     if (!SUCCEEDED(hr) || NULL == pSite)
  515.         return;
  516.  
  517.     //  See if this CSite already has a Property Bag
  518.     pPropertyBag = pSite->GetPropertyBag();
  519.  
  520.     //  If not create a new one and set it to the CSite
  521.     //  The CSite method will addRef it in either case
  522.     //
  523.     if (NULL == pPropertyBag)
  524.     {
  525.         // Create a brand new property bag
  526.         pPropertyBag = new (CPropertyBag());
  527.         hr = pSite->SetPropertyBag(pPropertyBag);
  528.  
  529.     }  
  530.  
  531.     //  Add the param to the property bag
  532.     VARIANT *pVar = new(VARIANT);
  533.     BSTR    pBstr = NULL;
  534.     
  535.     // Fill in the VARIANT structure
  536.     //
  537.     VariantInit(pVar);
  538.  
  539.     // Convert ANSI to Unicode  (copy 1)
  540.     hr = Ansi2Unicode(value, &pBstr);
  541.  
  542.     ASSERT(NULL != pBstr);
  543.     if(NULL == pBstr)
  544.         return;
  545.  
  546.     // VARIANT:BSTR's must be allocated with SysAllocString
  547.     //          (Copy 2)
  548.     pVar->vt = VT_BSTR;
  549.     pVar->bstrVal = SysAllocString(pBstr);
  550.  
  551.     // Don't need this copy any longer
  552.     GTR_FREE(pBstr);
  553.  
  554.     ASSERT(NULL != pVar->bstrVal);
  555.     if(NULL == pVar->bstrVal)
  556.         return;
  557.  
  558.     // This is a wrapper that handles the CString 
  559.     // to OLE string conversion
  560.     pPropertyBag->lpstrWrite(name, pVar);
  561.  
  562.     // pPropertyBag->Write() made a copy  (3 copies all told, a bit excessive I think, necessary?)
  563.     // so SAFEDELETE() this now
  564.     SAFEDELETE( pVar );
  565.  
  566.     return;
  567. }
  568.  
  569. //======================================================================
  570. //
  571. // BUGBUG:HACKHACK davidna 5/7/96 Beta1 Hack
  572. //
  573. // Called when PropertyBag::read() is called with the Property name set 
  574. // to "%%pbcontents%%"
  575. //
  576. // Put the contents in a block of memory as name & value pairs. 
  577. // Stored as char FAR * 's
  578. // Return this in the callers Variant
  579. //
  580. // The storage goes like this:
  581. // We have pointers to the name/value pairs following the 
  582. // the count (ULONG) of items (or pairs) in m_pbContents
  583. //
  584. // we allocate storage for all the values so we must them free later
  585. //
  586. //
  587. HRESULT CPropertyBag::GetPropertyBagContents
  588. (
  589.     VARIANT *pVar,
  590.     IErrorLog *pErrorLog
  591. )
  592. {
  593.  
  594.     PROPERTYLIST *pCurProp = m_pPropertiesHead;
  595.     char far * *pContents;
  596.  
  597.     ASSERT(NULL != pVar);
  598.     if(NULL == pVar)
  599.         return E_POINTER;
  600.  
  601.     // Must be this type for this HACK!
  602.     if(pVar->vt != (VT_BYREF|VT_UI1) )
  603.         return E_INVALIDARG;
  604.  
  605.     if(NULL == m_pPropertiesHead)
  606.         return E_FAIL;
  607.  
  608.     // only allowed to do this once
  609.     //
  610.     ASSERT(NULL == m_pbContents);
  611.     if(NULL != m_pbContents)
  612.         return E_FAIL;
  613.  
  614.     // allocate storage for the count (ULONG/number of items)
  615.     // plus each of the items or name/value pairs
  616.     //
  617.     m_pbContents = (LPSTR *) GTR_MALLOC(sizeof(ULONG) + (sizeof(char far *) * m_cProperties)*2);
  618.  
  619.     ULONG *pcItems = (ULONG *)m_pbContents;
  620.     *(ULONG *)m_pbContents = m_cProperties;   // first ULONG is count of items
  621.  
  622.     // Bump the pointer past the length
  623.     pContents = (char * *) ((ULONG)m_pbContents + sizeof(ULONG));
  624.  
  625.     // Fill in the blanks
  626.     while(pCurProp != NULL)
  627.     {
  628.         ASSERT((NULL != pCurProp->pProperty) && (NULL != pCurProp->pProperty->pszName));
  629.         if(((NULL != pCurProp->pProperty) && (NULL != pCurProp->pProperty->pszName)))
  630.         {
  631.             // store a pointer to the name
  632.             //
  633.             *pContents = pCurProp->pProperty->pszName;
  634.             pContents++;
  635.  
  636.             // Now store a pointer to the value
  637.             //  Convert to ansi 'cause Java Applets probably won't like BSTR's
  638.             //   and they're our only users of this
  639.             //
  640.             // Ansi2Unicode allocates the memory so we need to remember 
  641.             // to deallocate this later
  642.             //
  643.             if(FAILED(Unicode2Ansi((pCurProp->pProperty->pvarValue->bstrVal), pContents)) )
  644.             {
  645.                 GTR_FREE(*pContents);
  646.                 GTR_FREE(m_pbContents);
  647.                 m_pbContents = NULL;
  648.                 return E_FAIL;
  649.             }
  650.             else
  651.                 pContents++;
  652.         }
  653.         pCurProp = (PROPERTYLIST *)pCurProp->pNext;
  654.     }
  655.  
  656.     // put in variant
  657.     pVar->pbVal    = (unsigned char *)m_pbContents;
  658.  
  659.     return S_OK;
  660. }
  661.  
  662. #endif
  663.